package com.baymax.test.scene

import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.SurfaceTexture
import android.media.MediaPlayer
import android.os.Build
import android.util.Log
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.view.TextureView
import android.view.View
import android.view.WindowManager
import android.widget.FrameLayout
import androidx.annotation.RequiresApi
import androidx.core.view.isVisible
import kotlinx.coroutines.*
import tv.danmaku.ijk.media.player.AndroidMediaPlayer
import tv.danmaku.ijk.media.player.IMediaPlayer
import tv.danmaku.ijk.media.player.IjkMediaPlayer
import tv.danmaku.ijk.media.player.misc.IMediaDataSource
import java.io.File
import java.io.FileDescriptor
import java.io.FileInputStream
import kotlin.coroutines.CoroutineContext
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.measureTime

/**
 * @title:       标题
 * @project:     TestScene
 * @package:     com.baymax.test.scene
 * @class:       Player
 * @description:
 * <p>
 * 类描述
 * </p>
 *
 * @version:   1.0
 * @createDate:   2023/4/27 14:33
 * @Copyright (C) 2023 YSTEN
 * @author:       xuyujian
 */
class Player(context: Context) : FrameLayout(context), CoroutineScope,
    IMediaPlayer.OnCompletionListener, IMediaPlayer.OnErrorListener, IMediaPlayer.OnInfoListener {

    private var logger: (String) -> Unit = { Log.d("Player-V", it) }
    var job: Job = Job()
    var mediaPlayer: IMediaPlayer? = null
    var isLoop = false
    var url: (IMediaPlayer) -> Unit = {}
    var seek: Int = -1
    var prepareCheck: Duration = 6.seconds
    var seekWait: Duration = Duration.ZERO
    var decoderId: Decoder = Decoder.IJK


    override val coroutineContext: CoroutineContext = MainScope().coroutineContext
    val textureView: TextureView = TextureView(context).apply {
        visibility = GONE
        surfaceTextureListener = object : TextureView.SurfaceTextureListener {
            override fun onSurfaceTextureAvailable(
                surface: SurfaceTexture,
                width: Int,
                height: Int
            ) {
                logger("onSurfaceTextureAvailable")
                keepScreenOn(textureView, true)
                doStart {
                    this.setSurface(Surface(surface))
                }

            }

            override fun onSurfaceTextureSizeChanged(
                surface: SurfaceTexture,
                width: Int,
                height: Int
            ) {
                logger("onSurfaceTextureSizeChanged")
            }

            override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
                keepScreenOn(textureView, false)
                logger("onSurfaceTextureDestroyed")
                job.cancel()
                mediaPlayer?.release()
                return true
            }

            override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
//                logger("onSurfaceTextureUpdated")
            }

        }
    }

    val surfaceView: SurfaceView = object : SurfaceView(context) {
        override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
            super.onLayout(changed, left, top, right, bottom)
            logger("surface layout $left-$top-$right-$bottom")
        }
    }.apply {


        setZOrderMediaOverlay(true)
//        setZOrderOnTop(true)
        visibility = GONE
        holder.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceCreated(holder: SurfaceHolder) {
                logger("surfaceCreated")
                logger("surfaceCreated ${holder.surface}")
                keepScreenOn(surfaceView, true)
                doStart {
                    setDisplay(holder)
                }
            }

            override fun surfaceChanged(
                holder: SurfaceHolder, format: Int, width: Int, height: Int
            ) {
                logger("surfaceChanged $width $height ,${this@apply}")
            }

            override fun surfaceDestroyed(holder: SurfaceHolder) {
                keepScreenOn(surfaceView, false)

                logger("surfaceDestroyed")
                job.cancel()
                mediaPlayer?.release()

            }
        })
    }

    private var displayView: View = surfaceView

    val showView: View get() = displayView

    val cover: View = View(context).also { cover ->
        cover.setBackgroundResource(R.drawable.ic_launcher_background)
        addView(cover)
//        this.visibility = View.GONE
    }

    private fun doStart(prepared: IMediaPlayer.() -> Unit) {
        logger("doStart1")
        job.cancel()
        job = launch(Dispatchers.IO) {
            logger("doStart2")
            mediaPlayer?.setOnInfoListener(null)
            mediaPlayer?.setOnSeekCompleteListener(null)
            mediaPlayer?.setOnCompletionListener(null)
            mediaPlayer?.setOnErrorListener(null)
            mediaPlayer?.release()
            yield()
            logger("doStart3")
            mediaPlayer = createPlayer(decoderId).apply {
                val player = this
                logger("doStart3-init  $this")
                setOnInfoListener(this@Player)
//                setOnSeekCompleteListener(this@Player)
                setOnCompletionListener(this@Player)
                setOnErrorListener(this@Player)
                url(this)
                logger("doStart4")
                yield()
                prepareFor(this@launch, this)
                logger("doStart5")
                yield()
//                setDisplay(holder)
                prepared()
                yield()
                if (seek > 0) {
                    val job = launch {
                        logger("doStart5-1 seek :$seek")
                        val cost = measureTime {
                            seekAndWait(this, player, seek)
                        }
                        logger("doStart5-1 seek end:$cost")
                    }
                    job.join()
                }
                logger("doStart6")
                yield()
                start()
                logger("doStart7")
            }
        }
    }

    private suspend fun seekAndWait(scope: CoroutineScope, mediaPlayer: IMediaPlayer, seek: Int) {
        val wait = if (seekWait > Duration.ZERO) {
            scope.launch(Dispatchers.Default) {
                delay(seekWait)
                logger("seek timeout $seekWait ")
                logger("seeks超时...")
                job.cancel()
            }
        } else {
            Job().apply(CompletableJob::complete)
        }
        logger("seekAndWait  ")
        suspendCancellableCoroutine<Unit> { cont ->
            logger("seekAndWait1 ")
            mediaPlayer.setOnSeekCompleteListener {
                logger("seekAndWait2 ")
                wait.cancel()
                logger("seek起播....")
                cont.resumeWith(Result.success(Unit))
            }
            mediaPlayer.seekTo(seek.toLong())
            mediaPlayer.setOnSeekCompleteListener(null)
        }

    }

    private suspend fun prepareFor(scope: CoroutineScope, mediaPlayer: IMediaPlayer) {
        logger("prepareFor01 ")
        val wait = if (prepareCheck > Duration.ZERO) {
            scope.launch(Dispatchers.Default) {
                delay(prepareCheck)
                logger("prepareFor timeout 6s ")
                logger("起播超时$prepareCheck...")
                job.cancel()
            }
        } else {
            Job().apply(CompletableJob::complete)
        }
        logger("prepareFor02 ")
        val cost = measureTime {
            logger("prepareFor1 ")
            suspendCancellableCoroutine<Unit> { cont ->
                mediaPlayer.setOnPreparedListener {
                    logger("prepareFor2  complete total:${mediaPlayer.duration}")
                    wait.cancel()
                    logger("起播....")
                    cont.resumeWith(Result.success(Unit))
                }
                mediaPlayer.prepareAsync()
            }
        }
        logger("prepareFor1 end $cost")
    }

    @RequiresApi(Build.VERSION_CODES.M)
    fun start(file: File) {
        this.url = { it.setDataSource(MediaIOSource(file.inputStream(), file.length())) }
        startOK()
    }

    private fun startOK(seek: Int = 0) {
        var doStart = true
        if (displayView.parent == null) {
            addView(displayView, 0, generateDefaultLayoutParams())
            doStart = false
        }
        if (displayView.visibility != VISIBLE) {
            logger("开始播放....1")
            displayView.visibility = VISIBLE
            displayView.post {
                logger("开始播放...post${displayView}")
            }
            doStart = false
        }
        logger("开始播放....2 $displayView ,${displayView.layoutParams.width}-${displayView.layoutParams.height} ,${displayView.measuredWidth}-${displayView.measuredHeight}")
        if (doStart) {
            doStart {
                if (surfaceView.isVisible) {
                    this.setSurface(surfaceView.holder.surface)
                }

                if (textureView.isVisible) {
                    this.setSurface(Surface(textureView.surfaceTexture))
                }
            }
        }
    }

    fun start(url: String = "", seek: Int = -1) {
        logger("start url $url ,seek=$seek")
        this.url = {
            logger("url=$url ,seek=$seek")
            it.setDataSource(url)
        }
        startOK(seek)
    }

    fun stop() {
        logger("停止播放... $displayView")
        isLoop = false
        displayView.visibility = GONE
        cover.visibility = VISIBLE
        mediaPlayer?.release()
    }

    fun startLoop(url: String = "") {
        this.url = { it.setDataSource(url) }
        isLoop = true
        start(url)
    }

    val txtPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        textSize = 20f
        color = Color.GREEN
    }

    override fun dispatchDraw(canvas: Canvas) {
        super.dispatchDraw(canvas)
        canvas.drawText(displayView.javaClass.simpleName, 30f, height / 2f - 40, txtPaint)
        canvas.drawText("decoder-$decoderId", 30f, height / 2f, txtPaint)
        canvas.drawText(
            "display:${if (displayView.parent != null) 1 else 0},${displayView.width}-${displayView.height}",
            30f,
            height / 2f + 40,
            txtPaint
        )
    }

    override fun onCompletion(mp: IMediaPlayer) {
        doStart {
            if (surfaceView.isVisible) {
                this.setDisplay(surfaceView.holder)
            } else if (textureView.isVisible) {
                this.setSurface(Surface(textureView.surfaceTexture))
            }
        }
    }

    override fun onError(mp: IMediaPlayer, what: Int, extra: Int): Boolean {
        logger("播放失败....${what} - $extra")
        stop()
        job.cancel()
        mediaPlayer?.release()
        return true
    }


    fun setLogger(log: (String) -> Unit) {
        this.logger = log
    }

    override fun onInfo(mp: IMediaPlayer, what: Int, extra: Int): Boolean {
        if (mp == mediaPlayer) {
            if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
                logger("onInfo  renderCallBack $mp == $mediaPlayer (${renderCallBack})")
                renderCallBack()
            }
            return true
        }
        return false
    }

    @RequiresApi(Build.VERSION_CODES.M)
    fun start(mediaDataSource: IMediaDataSource) {
        url = { it.setDataSource(mediaDataSource) }
        startOK()
    }

    fun start(dirtyFd: FileDescriptor) {
        logger("start $dirtyFd")
        url = { it.setDataSource(dirtyFd) }
        startOK()
    }

    fun switchSurface() {
        stop()
        this.removeView(displayView)
        displayView = surfaceView
        addView(displayView, generateDefaultLayoutParams())
        logger("switchSurface $displayView")
    }

    fun switchTexture() {
        stop()
        this.removeView(displayView)
        displayView = textureView
        addView(displayView, generateDefaultLayoutParams())
        logger("switchTexture $displayView")

    }

    fun switchDecoder(id: Decoder) {
        stop()
        this.decoderId = id
    }


    var renderCallBack: () -> Unit = {
        logger("renderCallBack hide cover")
        cover.visibility = GONE
    }

    @RequiresApi(Build.VERSION_CODES.M)
    class MediaIOSource(private val stream: FileInputStream, private val len: Long) :
        IMediaDataSource {
        override fun close() {
            stream.close()
        }

        override fun readAt(position: Long, buffer: ByteArray?, offset: Int, size: Int): Int {
            stream.channel.position(position)
            return stream.read(buffer, offset, size)
        }

        override fun getSize(): Long {
            return len
        }

    }


    fun keepScreenOn(v: View, screenOn: Boolean) {
        (v.parent as View).setKeepScreenOn(screenOn)

        var manager = v.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        println("$v -> rootView ${v.rootView} ,wm=${manager} ,lp=${v.rootView.layoutParams}")
//        manager.updateViewLayout(v.rootView, v.rootView.layoutParams)
//        println("content context ${v.rootView?.findViewById<View>(android.view.Window.ID_ANDROID_CONTENT)?.context}")
//        v.rootView?.requestLayout()

//        if (screenOn) {
//            getWindowByView(v)?.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
//        } else {
//            getWindowByView(v)?.clearFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
//        }
    }

    private fun getWindowByView(view: View): android.view.Window? {
        var context: Context? = view.context
        while (context is ContextWrapper) {
            context = context.baseContext
            if (context is Activity) {
                return (context as Activity)?.window
            }
        }
        return null
    }

    private fun createPlayer(type: Decoder): IMediaPlayer {
        when (type) {
            Decoder.IJK -> {
                return IjkMediaPlayer().also { ijkMediaPlayer ->

                    ijkMediaPlayer.setOption(
                        IjkMediaPlayer.OPT_CATEGORY_PLAYER,
                        "start-on-prepared",
                        0
                    );
                    //http检测范围支持
                    ijkMediaPlayer.setOption(
                        IjkMediaPlayer.OPT_CATEGORY_FORMAT,
                        "http-detect-range-support",
                        0
                    );
                    //对于ijkPlayer的设置,有如下两种视频格式，0为软解，1为硬解
                    ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);
                    // 是否设置OpenSL ES引擎
                    ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 0);
                    //开启环路滤波（0比48清楚，但解码开销大，48基本没有开启环路滤波，清晰度低，解码开销小）
//        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 0);
                    ijkMediaPlayer.setOption(
                        IjkMediaPlayer.OPT_CATEGORY_CODEC,
                        "skip_loop_filter",
                        48
                    );
                    //overlay格式，有SDL_FCC_YV12、SDL_FCC_RV16、SDL_FCC_RV32 （SDL_FCC_RV32高质量显示）默认：SDL_FCC_YV12
//        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32);
                    // 跳帧处理,放CPU处理较慢时，进行跳帧处理，保证播放流程，画面和声音同步
                    ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1);
                    //设置播放前的最大探测时间
                    ijkMediaPlayer.setOption(
                        IjkMediaPlayer.OPT_CATEGORY_FORMAT,
                        "analyzemaxduration",
                        100L
                    );
                    //播放前的探测Size，默认是1M, 改小一点会出画面更快，单位为字节,但是过小部分视频会没有声音
                    ijkMediaPlayer.setOption(
                        IjkMediaPlayer.OPT_CATEGORY_FORMAT,
                        "probesize",
                        819200L
                    );
                    //每处理一个packet之后刷新io上下文
                    ijkMediaPlayer.setOption(
                        IjkMediaPlayer.OPT_CATEGORY_FORMAT,
                        "flush_packets",
                        1L
                    );
                    //是否开启预缓冲，一般直播项目会开启，达到秒开的效果，不过带来了播放丢帧卡顿的体验(如果频繁卡顿，
                    //可以保留缓冲区，不设置默认为1)
//        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0L);
                    //支持协议的白名单
                    ijkMediaPlayer.setOption(
                        IjkMediaPlayer.OPT_CATEGORY_FORMAT,
                        "protocol_whitelist",
                        "rtp,crypto,file,http,https,tcp,tls,udp"
                    );
                    //设置硬解码方式
                    ijkMediaPlayer.setOption(
                        IjkMediaPlayer.OPT_CATEGORY_PLAYER,
                        "mediacodec_mpeg4",
                        0L
                    );
                    //设置是否开启变调
                    ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "soundtouch", 1L);

                    //加快seek速度----------
                    //关闭精准寻帧功能，提高seek速度但可能降低定位精度
                    ijkMediaPlayer.setOption(
                        IjkMediaPlayer.OPT_CATEGORY_PLAYER,
                        "enable-accurate-seek",
                        0
                    );
                    //启用快速seek标志，加快视频跳转速度
                    ijkMediaPlayer.setOption(
                        IjkMediaPlayer.OPT_CATEGORY_FORMAT,
                        "fflags",
                        "fastseek"
                    );
                    //播放音视频时最多可以缓存多少数据，影响播放的流畅性和内存占用（默认15M）
//        ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", 512);


                }
            }

            else -> {
                return AndroidMediaPlayer()
            }
        }
    }


    sealed interface Decoder {
        data object Sys : Decoder
        data object IJK : Decoder
    }
}